﻿using AForge.Imaging;
using ImageProperties.Misc;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ImageProperties
{ 
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private PixelRGBA[,] imagePixelsColor;
        private double[,] imagePixelsLuminosity;
        private Size imageSize;

        public event PropertyChangedEventHandler PropertyChanged;
        ImageStatisticsHSL hslStatistics;
        ImageStatistics rgbStatistics;
        private String localImagePath = null;
        private PointCollection luminanceHistogramPoints = null;
        private PointCollection redColorHistogramPoints = null;
        private PointCollection greenColorHistogramPoints = null;
        private PointCollection blueColorHistogramPoints = null;

        private List<List<Rectangle>> rectanglesOverlay;
        private List<List<Visibility>> rectanglesOverlayPreviousVisibility;

        public String LocalImagePath
        {
            get
            {
                return this.localImagePath;
            }
            set
            {
                if (this.localImagePath != value)
                {
                    this.localImagePath = value;
                    if (this.PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("LocalImagePath"));
                        ImageChanged();
                    }                    
                }
            }
        }

        public bool PerformHistogramSmoothing { get; set; }

        public PointCollection LuminanceHistogramPoints
        {
            get
            {
                return this.luminanceHistogramPoints;
            }
            set
            {
                if (this.luminanceHistogramPoints != value)
                {
                    this.luminanceHistogramPoints = value;
                    if (this.PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("LuminanceHistogramPoints"));
                    }
                }
            }
        }

        public PointCollection RedColorHistogramPoints
        {
            get
            {
                return this.redColorHistogramPoints;
            }
            set
            {
                if (this.redColorHistogramPoints != value)
                {
                    this.redColorHistogramPoints = value;
                    if (this.PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("RedColorHistogramPoints"));
                    }
                }
            }
        }

        public PointCollection GreenColorHistogramPoints
        {
            get
            {
                return this.greenColorHistogramPoints;
            }
            set
            {
                if (this.greenColorHistogramPoints != value)
                {
                    this.greenColorHistogramPoints = value;
                    if (this.PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("GreenColorHistogramPoints"));
                    }
                }
            }
        }

        public PointCollection BlueColorHistogramPoints
        {
            get
            {
                return this.blueColorHistogramPoints;
            }
            set
            {
                if (this.blueColorHistogramPoints != value)
                {
                    this.blueColorHistogramPoints = value;
                    if (this.PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("BlueColorHistogramPoints"));
                    }
                }
            }
        }

        public MainWindow()
        {
            this.DataContext = this;
            InitializeComponent();
            this.mainImage.MouseMove += mainImage_MouseMove;            
            //this.PropertyChanged += MainWindow_PropertyChanged;
            this.Loaded += MainWindow_Loaded;

            this.rectanglesOverlay = new List<List<Rectangle>>(4);
            for (int x = 0; x < 4; ++x)
            {
                this.rectanglesOverlay.Add(new List<Rectangle>(4));

                for (int y = 0; y < 4; ++y)
                {
                    Rectangle newRectangleOverlay = new Rectangle();
                    newRectangleOverlay.Stretch = Stretch.Fill;
                    newRectangleOverlay.SetValue(Grid.ColumnProperty, x);
                    newRectangleOverlay.SetValue(Grid.RowProperty, y);
                    newRectangleOverlay.Opacity = 0.5;
                    newRectangleOverlay.Fill = new SolidColorBrush(Colors.Aqua);
                    newRectangleOverlay.MouseMove += rectangleOverlay_MouseMove;
                    newRectangleOverlay.MouseDown += rectangleOverlay_MouseDown;

                    this.rectanglesOverlay[x].Add(newRectangleOverlay);
                }
            }

            this.rectanglesOverlayPreviousVisibility = new List<List<Visibility>>(4);
            for (int x = 0; x < 4; ++x)
            {
                this.rectanglesOverlayPreviousVisibility.Add(new List<Visibility>(4));

                for (int y = 0; y < 4; ++y)
                {
                    this.rectanglesOverlayPreviousVisibility[x].Add(Visibility.Collapsed);
                }
            }
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                this.LocalImagePath = System.IO.Path.GetFullPath(@"..\..\Data\samplePic.jpg");
            }
            catch (Exception ex)
            {
            }            
        }

        void rectangleOverlay_MouseMove(object sender, MouseEventArgs e)
        {
            Point rectanglePosition = this.GetOverlayRectanglePosition((Rectangle)sender);
            Point mouseRelativePosition = e.GetPosition((Rectangle)sender);
            Point mousePosition = new Point(((Rectangle)sender).ActualWidth * rectanglePosition.X + mouseRelativePosition.X,
                ((Rectangle)sender).ActualHeight * rectanglePosition.Y + mouseRelativePosition.Y);

            PixelRGBA pixelSelected = GetPixelFromImageUsingMousePosition(this.mainImage, this.imagePixelsColor, mousePosition);
            this.pixelColorRectangle.Fill = new SolidColorBrush(Color.FromArgb((byte)pixelSelected.Alpha, (byte)pixelSelected.Red, (byte)pixelSelected.Green, (byte)pixelSelected.Blue));
            this.pixelColorRedText.Text = pixelSelected.Red.ToString();
            this.pixelColorGreenText.Text = pixelSelected.Green.ToString();
            this.pixelColorBlueText.Text = pixelSelected.Blue.ToString();
            double pixelLuminosity = pixelSelected.Red * 0.3 + pixelSelected.Green * 0.59 + pixelSelected.Blue * 0.11;
            this.pixelColorLuminosityText.Text = ((int)pixelLuminosity).ToString();

            Point positionInImage = new Point(mousePosition.X * (this.imagePixelsColor.GetLength(0) / this.mainImage.ActualWidth),
                                              mousePosition.Y * (this.imagePixelsColor.GetLength(1) / this.mainImage.ActualHeight));

            this.positionText.Text = ((int)positionInImage.X).ToString() + ":" + ((int)positionInImage.Y).ToString();
        }

        void rectangleOverlay_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Rectangle rectangleSender = (Rectangle)sender;
            if (rectangleSender.Opacity == 0.5)
            {
                rectangleSender.Opacity = 0.0;
            }
            else
            {
                rectangleSender.Opacity = 0.5;
            }
        }

        private void ImageChanged()
        {
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(this.localImagePath);
            
            this.resolutionText.Text = bitmap.Width.ToString() + "x" + bitmap.Height.ToString();

            int bitsPerPixel = 0;
            switch (bitmap.PixelFormat)
            {
                case System.Drawing.Imaging.PixelFormat.Format1bppIndexed:
                    bitsPerPixel = 1;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format4bppIndexed:
                    bitsPerPixel = 4;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
                    bitsPerPixel = 8;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format16bppArgb1555:
                case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:
                case System.Drawing.Imaging.PixelFormat.Format16bppRgb555:
                case System.Drawing.Imaging.PixelFormat.Format16bppRgb565:
                    bitsPerPixel = 16;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
                    bitsPerPixel = 24;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                case System.Drawing.Imaging.PixelFormat.Format32bppPArgb:
                case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
                    bitsPerPixel = 32;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format48bppRgb:
                    bitsPerPixel = 48;
                    break;
                case System.Drawing.Imaging.PixelFormat.Format64bppArgb:
                case System.Drawing.Imaging.PixelFormat.Format64bppPArgb:
                    bitsPerPixel = 64;
                    break;
                default:
                    break;
            }

            if (bitsPerPixel > 0)
            {
                this.colorDepthText.Text = bitsPerPixel.ToString() + " bits";
            }
            else
            {
                this.colorDepthText.Text = "unknown";
            }            

            this.hslStatistics = new ImageStatisticsHSL(bitmap);
            this.LuminanceHistogramPoints = this.ConvertToPointCollection(hslStatistics.Luminance.Values);

            this.rgbStatistics = new ImageStatistics(bitmap);
            this.RedColorHistogramPoints = this.ConvertToPointCollection(rgbStatistics.Red.Values);
            this.GreenColorHistogramPoints = this.ConvertToPointCollection(rgbStatistics.Green.Values);
            this.BlueColorHistogramPoints = this.ConvertToPointCollection(rgbStatistics.Blue.Values);
            
            this.FillImagePixelsFromBitmap((BitmapSource)this.mainImage.Source);
            imageSize = new Size(this.imagePixelsColor.GetLength(0), this.imagePixelsColor.GetLength(1));

            this.energyRedText.Text = this.GetImageEnergyRed().ToString();
            this.energyGreenText.Text = this.GetImageEnergyGreen().ToString();
            this.energyBlueText.Text = this.GetImageEnergyBlue().ToString();
            this.energyLuminosityText.Text = this.GetImageEnergyLuminosity().ToString();

            int redParts = 0;
            int greenParts = 0;
            int blueParts = 0;
            int skyParts = 0;
            List<List<Color>> dominantColors = GetDominantColors4x4();
            for (int x = 0; x < 4; ++x)
            {
                for (int y = 0; y < 4; ++y)
                {
                    this.rectanglesOverlay[x][y].Fill = new SolidColorBrush(dominantColors[x][y]);
                    if (dominantColors[x][y] == Colors.Red)
                    {
                        redParts++;
                    }
                    if (dominantColors[x][y] == Colors.Green)
                    {
                        greenParts++;
                    }
                    if (dominantColors[x][y] == Colors.Blue)
                    {
                        if (y <= 1)
                        {
                            skyParts++;
                        }

                        blueParts++;
                    }                    

                    try
                    {
                        int indexOfRectangle = this.imageGrid.Children.IndexOf(this.rectanglesOverlay[x][y]);
                        this.imageGrid.Children.RemoveAt(indexOfRectangle);
                    }
                    catch (Exception ex)
                    {
                    }
                    finally
                    {
                        this.imageGrid.Children.Add(this.rectanglesOverlay[x][y]);
                    }
                }
            }

            if (blueParts == 16)
            {
                this.probableImageTypeText.Text = "water/sky";
            }
            else
            {
                if (skyParts >= 6)
                {
                    this.probableImageTypeText.Text = "outdoors";
                }
                else
                {
                    if (greenParts >= 10)
                    {
                        this.probableImageTypeText.Text = "nature";
                    }
                    else
                    {
                        this.probableImageTypeText.Text = "unknown";
                    }
                }
            }
        }

        void mainImage_MouseMove(object sender, MouseEventArgs e)
        {
            Point mousePosition =  e.GetPosition(this.mainImage);
            PixelRGBA pixelSelected = GetPixelFromImageUsingMousePosition(this.mainImage, this.imagePixelsColor, mousePosition);
            this.pixelColorRectangle.Fill = new SolidColorBrush(Color.FromArgb((byte)pixelSelected.Alpha, (byte)pixelSelected.Red, (byte)pixelSelected.Green, (byte)pixelSelected.Blue));
            this.pixelColorRectangle.Fill = new SolidColorBrush(Color.FromArgb((byte)pixelSelected.Alpha, (byte)pixelSelected.Red, (byte)pixelSelected.Green, (byte)pixelSelected.Blue));
            this.pixelColorRedText.Text = pixelSelected.Red.ToString();
            this.pixelColorGreenText.Text = pixelSelected.Green.ToString();
            this.pixelColorBlueText.Text = pixelSelected.Blue.ToString();
            double pixelLuminosity = pixelSelected.Red * 0.3 + pixelSelected.Green * 0.59 + pixelSelected.Blue * 0.11;
            this.pixelColorLuminosityText.Text = ((int)pixelLuminosity).ToString();

            Point positionInImage = new Point(mousePosition.X * (this.imagePixelsColor.GetLength(0) / this.mainImage.ActualWidth),
                                              mousePosition.Y * (this.imagePixelsColor.GetLength(1) / this.mainImage.ActualHeight));

            this.positionText.Text = ((int)positionInImage.X).ToString() + ":" + ((int)positionInImage.Y).ToString();
        }

        private void loadImageButton_Click_1(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "All image types (*.bmp, *.jpg, *.jpeg, *.png)|*.bmp; *.jpg; *.jpeg; *.png|JPEG|*.jpg *.jpeg|BMP|*.bmp|All Files (*.*)|*.*";
            openFileDialog.Multiselect = false;

            Nullable<bool> result = openFileDialog.ShowDialog();

            if (result == true)
            {
                this.LocalImagePath = openFileDialog.FileName;
            }
        }

        private void FillImagePixelsFromBitmap(BitmapSource source)
        {
            if (source.Format != PixelFormats.Bgra32)
            {
                source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
            }

            int sourceWidth = source.PixelWidth;
            int sourceHeight = source.PixelHeight;
            int stride = sourceWidth * ((source.Format.BitsPerPixel + 7) / 8);

            byte[] sourcePixels = new byte[sourceHeight * sourceWidth * 4];
            source.CopyPixels(sourcePixels, stride, 0);

            this.imagePixelsColor = new PixelRGBA[sourceWidth, sourceHeight];
            this.imagePixelsLuminosity = new double[sourceWidth, sourceHeight];

            for (int x = 0; x < sourceWidth; ++x)
            {
                for (int y = 0; y < sourceHeight; ++y)
                {
                    this.imagePixelsColor[x, y] = new PixelRGBA(sourcePixels[(y * sourceWidth + x) * 4 + 2],
                                                                sourcePixels[(y * sourceWidth + x) * 4 + 1],
                                                                sourcePixels[(y * sourceWidth + x) * 4 + 0],
                                                                sourcePixels[(y * sourceWidth + x) * 4 + 3]);

                    this.imagePixelsLuminosity[x, y] = imagePixelsColor[x, y].Red * 0.3f
                        + imagePixelsColor[x, y].Green * 0.59f
                        + imagePixelsColor[x, y].Blue * 0.11f;
                }
            }
        }

        private PointCollection ConvertToPointCollection(int[] values)
        {
            values = SmoothHistogram(values);

            int max = values.Max();

            PointCollection points = new PointCollection();
            // first point (lower-left corner)
            points.Add(new Point(0, max));
            // middle points
            for (int i = 0; i < values.Length; i++)
            {
                points.Add(new Point(i, max - values[i]));
            }
            // last point (lower-right corner)
            points.Add(new Point(values.Length - 1, max));

            return points;
        }

        private int[] SmoothHistogram(int[] originalValues)
        {
            int[] smoothedValues = new int[originalValues.Length];

            double[] mask = new double[] { 0.25, 0.5, 0.25 };

            for (int bin = 1; bin < originalValues.Length - 1; bin++)
            {
                double smoothedValue = 0;
                for (int i = 0; i < mask.Length; i++)
                {
                    smoothedValue += originalValues[bin - 1 + i] * mask[i];
                }
                smoothedValues[bin] = (int)smoothedValue;
            }

            return smoothedValues;
        }

        private Point GetOverlayRectanglePosition(Rectangle rectangle)
        {
            for (int x = 0; x < this.rectanglesOverlay.Count; ++x)
            {
                for (int y = 0; y < this.rectanglesOverlay[x].Count; ++y)
                {
                    if (this.rectanglesOverlay[x][y] == rectangle)
                    {
                        return new Point(x, y);
                    }
                }
            }

            throw new KeyNotFoundException();
        }

        private bool IsOverlayVisible()
        {
            foreach (List<Rectangle> listRectangles in this.rectanglesOverlay)
            {
                foreach(Rectangle rectangle in listRectangles)
                {
                    if (rectangle.Visibility == System.Windows.Visibility.Visible)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private PixelRGBA GetPixelFromImageUsingMousePosition(System.Windows.Controls.Image imageRelativeTo, PixelRGBA[,] imagePixels, Point mousePosition)
        {
            Point positionInImage = new Point(mousePosition.X * (imagePixels.GetLength(0) / imageRelativeTo.ActualWidth),
                                              mousePosition.Y * (imagePixels.GetLength(1) / imageRelativeTo.ActualHeight));

            if (positionInImage.X < imagePixels.GetLength(0)
                && positionInImage.Y < imagePixels.GetLength(1))
            {
                return imagePixels[(int)positionInImage.X, (int)positionInImage.Y];
            }
            else
            {
                return new PixelRGBA(0, 0, 0, 0);
            }
        }

        private List<List<Color>> GetDominantColors4x4()
        {
            float red, blue, green;
            int line = 0;
            List<List<Color>> listToReturn = new List<List<Color>>(4);
            for (int i = 0; i < 4; ++i)
            {
                listToReturn.Add(new List<Color>(4));
            }

            int quarterWidth = (int)(Math.Truncate(imageSize.Width / 4));
            int quarterHeight = (int)(Math.Truncate(imageSize.Height / 4));

            for(int i=0; i<(quarterWidth * 4); i+=quarterWidth)
            {
                for(int j=0; j<(quarterHeight * 4); j+=quarterHeight)
                {
                    red = 0; blue = 0; green = 0;
                    for(int k=i; k<(i+quarterWidth); k++)
                    {
                        for(int l=j; l<(j+quarterHeight); l++)
                        {
                            red += imagePixelsColor[k, l].Red;
                            green += imagePixelsColor[k, l].Green;
                            blue += imagePixelsColor[k, l].Blue;
                        }
                    }

                    if ((red > green) && (red > blue))
                    {
                        listToReturn[line].Add(Colors.Red);
                    }
                    else if ((green > red) && (green > blue))
                    {
                        listToReturn[line].Add(Colors.Green);
                    }
                    else if ((blue > red) && (blue > green))
                    {
                        listToReturn[line].Add(Colors.Blue);
                    }
                }
                line++;
            }

            return listToReturn;
        }

        private double GetImageEnergyLuminosity()
        {
            double sum = 0;
            for (int i = 0; i < imageSize.Width; i++)
            {
                for (int j = 0; j < imageSize.Height; j++)
                {
                    sum += imagePixelsLuminosity[i, j] * imagePixelsLuminosity[i, j];
                }
            }
            return sum;
        }

        private double GetImageEnergyRed()
        {
            double sum = 0;
            for (int i = 0; i < imageSize.Width; i++)
            {
                for (int j = 0; j < imageSize.Height; j++)
                {
                    sum += imagePixelsColor[i, j].Red * imagePixelsColor[i, j].Red;
                }
            }
            return sum;
        }

        private double GetImageEnergyGreen()
        {
            double sum = 0;
            for (int i = 0; i < imageSize.Width; i++)
            {
                for (int j = 0; j < imageSize.Height; j++)
                {
                    sum += imagePixelsColor[i, j].Green * imagePixelsColor[i, j].Green;
                }
            }
            return sum;
        }

        private double GetImageEnergyBlue()
        {
            double sum = 0;
            for (int i = 0; i < imageSize.Width; i++)
            {
                for (int j = 0; j < imageSize.Height; j++)
                {
                    sum += imagePixelsColor[i, j].Blue * imagePixelsColor[i, j].Blue;
                }
            }
            return sum;
        }
        
        private void rectangleOverlayVisibilityToggle_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsOverlayVisible())
            {
                for (int x = 0; x < 4; ++x)
                {
                    for (int y = 0; y < 4; ++y)
                    {
                        this.rectanglesOverlayPreviousVisibility[x][y] = this.rectanglesOverlay[x][y].Visibility;
                        this.rectanglesOverlay[x][y].Visibility = System.Windows.Visibility.Hidden;
                    }
                }
            }
            else
            {
                for (int x = 0; x < 4; ++x)
                {
                    for (int y = 0; y < 4; ++y)
                    {
                        this.rectanglesOverlay[x][y].Visibility = this.rectanglesOverlayPreviousVisibility[x][y];
                    }
                }
            }
        }
    }
}
